import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";
// 引入rgbeLoad
// DataTextureLoader
// 用于加载二进制文件格式的(rgbe, hdr, ...)的抽象类。 内部使用FileLoader来加载文件， 和创建一个新的 DataTexture.
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"


//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 

//  透视相机（PerspectiveCamera）这一摄像机使用perspective projection（透视投影）来进行投影。
// 这一投影模式被用来模拟人眼所看到的景象，它是3D场景的渲染中使用得最普遍的投影模式。
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度   // aspect — 摄像机视锥体长宽比  // near — 摄像机视锥体近端面  // far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。
camera.position.set(0, 0, 20);
scene.add(camera); //添加相机到场景中


const cubeGeometry = new THREE.BoxBufferGeometry(2, 2, 2);
const material = new THREE.MeshBasicMaterial({
    wireframe: true
});
const redMaterial = new THREE.MeshBasicMaterial({
    color: "#ff0000"
});

// 生成立方体
var boxArr = [];
// 组（Group）
// 它几乎和Object3D是相同的，其目的是使得组中对象在语法上的结构更加清晰。
let boxGroup = new THREE.Group();
for (let i = 0; i <= 5; i++) {
    for (let j = 0; j <= 5; j++) {
        for (let z = 0; z <= 5; z++) {
            const cube = new THREE.Mesh(cubeGeometry, material);
            cube.position.set(i * 2 - 5, j * 2 - 5, z * 2 - 5);
            boxGroup.add(cube); //增加进组
            boxArr.push(cube);
        }
    }
}
scene.add(boxGroup);

const sjxGroup = new THREE.Group();
// 生成50个三角形
for (let i = 0; i < 50; i++) {

    const cubeGeometry = new THREE.BufferGeometry();
    // 顺序为xyz 三个值一个顶点数据 9个值为一个三角形
    const positionArray = new Float32Array(9);
    for (let i = 0; i < 9; i++) {
        positionArray[i] = Math.random() * 10 - 5; //-5到5
    }

    //BufferAttribute 利用 BufferAttribute，可以更高效的向GPU传递数据。
    //这个类用于存储与BufferGeometry相关联的 attribute 例如顶点位置向量，面片索引，法向量，颜色值，UV坐标以及任何自定义 
    cubeGeometry.setAttribute("position", new THREE.BufferAttribute(positionArray, 3));

    // 3.2创建物体材质 
    // 设置随机颜色与透明度
    const color = new THREE.Color(Math.random(), Math.random(), Math.random());
    const cubeMaterial = new THREE.MeshBasicMaterial({
        color: color, transparent: true,
        opacity: 0.9, side: THREE.DoubleSide
    });

    // 3.3根据几何体与材质创建物体
    const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
    sjxGroup.add(cube);
}
sjxGroup.position.y = -30;
scene.add(sjxGroup);


// 初始化球体与材质
const positGroup = new THREE.Group();
const geometry = new THREE.SphereGeometry(2, 32, 16);

const standardMaterial = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(geometry, standardMaterial);
// // 设置球体投射阴影
// .castShadow : Boolean
// 对象是否被渲染到阴影贴图中。默认值为false。
mesh.castShadow = true;
positGroup.add(mesh);

// 创建平面接收阴影
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const mesh1 = new THREE.Mesh(planeGeometry, standardMaterial);
mesh1.position.set(0, -2, 0);
mesh1.rotation.x = - Math.PI / 2;
// 设置平面接收投射阴影
// .receiveShadow : Boolean
// 材质是否接收阴影。默认值为false。
mesh1.receiveShadow = true;
positGroup.add(mesh1);


// 添加灯光
// 添加环境光
const light = new THREE.AmbientLight(0xffffff, 0.5);
// 添加PointLight
const positLight = new THREE.PointLight(0xff0000, 2);
positLight.castShadow = true;
positLight.shadow.radius = 40
positLight.shadow.mapSize.set(512 * 6, 512 * 6);
positLight.decay = 0;
positLight.distance = 20;

// 新增一个小球体把光源添加到光源下
const smallBall = new THREE.Mesh(
    new THREE.SphereGeometry(0.5, 20, 20),
    new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
// 设置球体位置
smallBall.position.set(5, 5, 5);
// 把光源添加到物体中
smallBall.add(positLight);

positGroup.add(light);
// 增加球体到场景
positGroup.add(smallBall);
positGroup.position.y = -60;
scene.add(positGroup);

const groupArr = [boxGroup, sjxGroup, positGroup];

// 创建投射光影
// 这个类用于进行raycasting（光线投射）。 光线投射用于进行鼠标拾取（在三维空间中计算出鼠标移过了什么物体）。
const raycaster = new THREE.Raycaster();

//鼠标的位置 
const mouse = new THREE.Vector2;

window.addEventListener("click", (event) => {
    mouse.x = event.clientX / window.innerWidth * 2 - 1;
    mouse.y = - (event.clientY / window.innerHeight * 2 - 1);
    raycaster.setFromCamera(mouse, camera);
    let result = raycaster.intersectObjects(boxArr);
    console.log(result)
    if (result.length > 0) {
        result[0].object.material = redMaterial;
    }
})

// 监听鼠标移动
window.addEventListener("mousemove", (event) => {
    mouse.x = event.clientX / window.innerWidth - 0.5; // -0.5~0.5
    mouse.y = event.clientY / window.innerHeight - 0.5;
})

// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
renderer.physicallyCorrectLights = true; // 新版本不用设置 物理正确光源 decay也会生效。 老版本开阴影要设置



// 设置时钟
// Clock该对象用于跟踪时间。如果performance.now可用，则 Clock 对象通过该方法实现，否则回落到使用略欠精准的Date.now来实现。
// .oldTime : Float 存储时钟最后一次调用 start, .getElapsedTime() 或 .getDelta() 方法的时间。默认值是 0。
const clock = new THREE.Clock();

gsap.to(boxGroup.rotation, {
    x: "+=" + Math.PI * 2,
    y: "-=" + Math.PI * 2,
    z: "+=" + Math.PI * 2,
    duration: 8,
    ease: "power2.inOut",
    repeat: -1,
})

gsap.to(sjxGroup.rotation, {
    x: "+=" + Math.PI * 2,
    z: "-=" + Math.PI * 2,
    duration: 8,
    ease: "power2.inOut",
    repeat: -1,
})

gsap.to(positGroup.rotation, {
    x: Math.PI / 10,
    y: Math.PI / 10,
    duration: 4,
    ease: "power2.inOut",
    repeat: -1,
    yoyo: true
})
gsap.to(smallBall.position, {
    x: -5,
    y: 3,
    duration: 1,
    ease: "power2.inOut",
    repeat: -1,
    yoyo: true
})

//圆周运动
gsap.to(smallBall.position, {
    z: -5,
    x: -5,
    duration: 3,
    ease: "power2.inOut",
    repeat: -1,
    yoyo: true
})

function render() {
    // let time = clock.getElapsedTime();
    let deltaTime = clock.getDelta();//两次间隔时间

    // boxGroup.rotation.x = time * 0.5;
    // boxGroup.rotation.y = time * 0.5;

    // sjxGroup.rotation.x = time * 0.5;
    // sjxGroup.rotation.z = time * 0.3;

    //  // 设置光源位置
    //  smallBall.position.x =  Math.sin(time) * 5;
    //  smallBall.position.z =  Math.cos(time) * 5;
    //  smallBall.position.y = 5 + Math.sin(time * 10);

    //  positGroup.rotation.x  =  Math.sin(time) * 0.05;
    //  positGroup.rotation.z  =  Math.sin(time) * 0.05;

    // 滚动时更新相机  
    camera.position.y = window.scrollY / window.innerHeight * -30;

    // 鼠标移动时更新相机
    camera.position.x  += (mouse.x * 8 - camera.position.x) * deltaTime * 5;

    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render()

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})

let currentPage = 0;

// 监听窗口滚动
window.addEventListener("scroll", () => {
    //当前页面y轴移动大于本页面高度50% 下一页
    // scrollY 垂直方向已滚动的像素值
    const newPage = Math.round(window.scrollY / window.innerHeight);
    // console.log(window.scrollY )
    if (newPage != currentPage) {
        currentPage = newPage;
        // console.log("当前页面为：" + currentPage);
        // console.log(groupArr[currentPage]);
        // 切屏后设置切屏特效
        gsap.to(groupArr[currentPage].rotation, {
            z: "+=" + Math.PI * 2,
            x: "+=" + Math.PI * 2,
            duration: 3,
            onComplete() {
                console.log("切屏特效完成");
            }
        })
        gsap.fromTo(`.page${currentPage} h1`, { x: -300 }, { x: 0, rotate: "+=360", duration: 1 });
    }
})
